"""实时监控

一张图中展示所有业务逻辑上的货物数量的变化
未处理
分类平台
normal dangerous refrigerated
agv取货点
已处理

总共有7条线

"""

from collections import deque
from dataclasses import dataclass, field
from sys import monitoring
from typing import Any, Optional
import numpy as np
import matplotlib.pyplot as plt
import time
import matplotlib.animation as animation
import matplotlib.patches as patches
import threading

from config.warehouse_config import GOODS_NUM
from models.processes import SimObjManager




@dataclass
class MonitorData:
    """定义需要监控的数据结构
    """
    timestamp:deque=field(default_factory=lambda:deque(maxlen=100))#时间戳
    unprocessed:deque=field(default_factory=lambda:deque(maxlen=100))#未处理
    type_pool:deque=field(default_factory=lambda:deque(maxlen=100))#类型分类平台
    normal_pool:deque=field(default_factory=lambda:deque(maxlen=100))#正常
    dangerous_pool:deque=field(default_factory=lambda:deque(maxlen=100))#危险
    refrigerated_pool:deque=field(default_factory=lambda:deque(maxlen=100))#冷冻
    agv_pickup:deque=field(default_factory=lambda:deque(maxlen=100))#agv 取货点
    processed:deque=field(default_factory=lambda:deque(maxlen=100))# 已处理

plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False
@dataclass
class RealtimeMonitor:
    """实时监控"""
    data:MonitorData=field(default_factory=MonitorData)
    update_interval:int=field(default=1000)# 更新间隔
    is_running:bool=field(default=False)
    lines:dict=field(default_factory=lambda:{})
    _animation:Optional[animation.FuncAnimation]=field(default=None)



    def __post_init__(self)->None:
        """初始化实时监控"""
        # 创建图表
        self.fig,self.ax=plt.subplots()
        self.setup_plot()

        self.lines={}
        self.setup_lines()

    
    def setup_plot(self)->None:
        """设置图表"""
        self.ax.set_xlabel("时间",fontsize=12)
        self.ax.set_ylabel("数量",fontsize=12)
        self.ax.set_title("货物数量变化")
        self.ax.grid(True,alpha=0.3)
        self.ax.set_xlim(0,100)
        self.ax.set_ylim(0,100)


    def setup_lines(self)->None:
        """设置折线"""
        line_configs = [
            ('unprocessed', '未处理', '#FF6B6B', '-'),
            ('type_pool', '分类平台', '#4ECDC4', '-'),
            ('normal_pool', 'Normal类型池', '#45B7D1', '-'),
            ('dangerous_pool', 'Dangerous类型池', '#FFA07A', '-'),
            ('refrigerated_pool', 'Refrigerated类型池', '#98D8C8', '-'),
            ('agv_pickup', 'AGV取货点', '#F7DC6F', '-'),
            ('processed', '已处理', '#BB8FCE', '-')
        ]
        for key, label, color, style in line_configs:
            line, = self.ax.plot([], [], 
                               label=label, 
                               color=color, 
                               linestyle=style,
                               linewidth=2,
                               marker='o',
                               markersize=3)
            self.lines[key] = line
        self.ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
        plt.tight_layout()

    def updata_data(self,timestamp:float,**data_points)->None:
        """更新监控系统

        Args:
            timestamp (float): 时间戳，可以直接传入 env 的 模拟时间
            **data_points:各类型货物数量的数据 
                - unprocessed: 未处理货物数量
                - type_pool: 分类平台货物数量
                - normal_pool: normal类型池货物数量
                - dangerous_pool: dangerous类型池货物数量
                - refrigerated_pool: refrigerated类型池货物数量
                - agv_pickup: agv取货点货物数量
                - processed: 已处理货物数量
        """
        # 记录更新前的数据长度
        lengths_before = {
            'timestamp': len(self.data.timestamp),
            'unprocessed': len(self.data.unprocessed),
            'type_pool': len(self.data.type_pool),
            'normal_pool': len(self.data.normal_pool),
            'dangerous_pool': len(self.data.dangerous_pool),
            'refrigerated_pool': len(self.data.refrigerated_pool),
            'agv_pickup': len(self.data.agv_pickup),
            'processed': len(self.data.processed)
        }

        self.data.timestamp.append(timestamp)
        # 更新数据
        self.data.unprocessed.append(data_points.get('unprocessed', 0))
        self.data.type_pool.append(data_points.get('type_pool', 0))
        self.data.normal_pool.append(data_points.get('normal_pool', 0))
        self.data.dangerous_pool.append(data_points.get('dangerous_pool', 0))
        self.data.refrigerated_pool.append(data_points.get('refrigerated_pool', 0))
        self.data.agv_pickup.append(data_points.get('agv_pickup', 0))
        self.data.processed.append(data_points.get('processed', 0))

        # 记录更新后的数据长度
        lengths_after = {
            'timestamp': len(self.data.timestamp),
            'unprocessed': len(self.data.unprocessed),
            'type_pool': len(self.data.type_pool),
            'normal_pool': len(self.data.normal_pool),
            'dangerous_pool': len(self.data.dangerous_pool),
            'refrigerated_pool': len(self.data.refrigerated_pool),
            'agv_pickup': len(self.data.agv_pickup),
            'processed': len(self.data.processed)
        }
        # 检查数据一致性
        unique_lengths_before = set(lengths_before.values())
        unique_lengths_after = set(lengths_after.values())
        
        if len(unique_lengths_before) > 1:
            print(f"警告：更新前数据长度不一致 - {lengths_before}")
            
        if len(unique_lengths_after) > 1:
            print(f"错误：更新后数据长度不一致 - {lengths_after}")

    
    def animate(self,frame):
        """更新动画的函数

        Args:
            frame (_type_): _description_
        """
        if len(self.data.timestamp) <1:
            return list(self.lines.values())
        # 转换为列表用于绘图
        x_data  = list(self.data.timestamp)
        # 更新每条线的数据
        data_mappings={
            'unprocessed': list(self.data.unprocessed),
            'type_pool': list(self.data.type_pool),
            'normal_pool': list(self.data.normal_pool),
            'dangerous_pool': list(self.data.dangerous_pool),
            'refrigerated_pool': list(self.data.refrigerated_pool),
            'agv_pickup': list(self.data.agv_pickup),
            'processed': list(self.data.processed)
        }
        # 确保所有数据长度一致，取最小长度
        all_lengths = [len(x_data)] + [len(y_list) for y_list in data_mappings.values()]
        min_length = min(all_lengths) if all_lengths else 0
        
        if min_length == 0:
            return list(self.lines.values())
            
        # 截取到相同长度
        x_data = x_data[:min_length]
        
        # 关键修复：必须同时设置x和y数据，且使用截取后的数据
        for key, line in self.lines.items():
            if key in data_mappings:
                y_data = data_mappings[key][:min_length]
                # 确保x和y数据长度完全一致
                if len(x_data) == len(y_data):
                    line.set_xdata(x_data)
                    line.set_ydata(y_data)
                else:
                    print(f"警告：{key}的x({len(x_data)})和y({len(y_data)})数据长度不匹配")
                    line.set_xdata([])
                    line.set_ydata([])
        
        # 更新坐标轴范围
        if len(x_data) > 0:
            self.ax.set_xlim(max(0, x_data[-1] - 50), x_data[-1] + 10)
            
            # 计算y轴范围时使用截取后的数据
            all_y_data = []
            for key in data_mappings:
                y_list = data_mappings[key][:min_length]
                if len(y_list) > 0:
                    all_y_data.extend(y_list)
            
            if all_y_data:
                max_y = max(all_y_data)
                min_y = min(all_y_data)
                # 设置y轴范围，添加一些边距
                self.ax.set_ylim(max(0, min_y - 5), max_y + 10)
        
        return list(self.lines.values())
    
    def start_monitoring(self)->None:
        """开始监控"""
        if self.is_running:return

        self.is_running=True
        self._animation=animation.FuncAnimation(self.fig,self.animate,interval=self.update_interval,blit=False,cache_frame_data=False,repeat=True)

    def stop_monitoring(self)->None:
        """停止监控"""
         
        self.is_running=False
        if self._animation:
            self._animation.event_source.stop()
            self._animation=None

    def show(self):
        self.start_monitoring()
        try:
            plt.show()
        except KeyboardInterrupt:
            self.stop_monitoring()
        finally:
            if hasattr(self,'fig'):
                plt.close(self.fig)
    
    def save_plot(self,file_name:str)->None:
        """保存图片"""
        plt.savefig(file_name)

    def clear_data(self):
        self.data=MonitorData()

# 调用示例代码
def demo_monitor()->None:
    monitor=RealtimeMonitor()
    def simulate_data():
        import random
        timestamp=0
        while monitor.is_running:
            monitor.updata_data(
                timestamp=timestamp,
                unprocessed=random.randint(0,50),
                type_pool=random.randint(0, 30),
                normal_pool=random.randint(0, 40),
                dangerous_pool=random.randint(0, 20),
                refrigerated_pool=random.randint(0, 25),
                agv_pickup=random.randint(0, 35),
                processed=random.randint(0, 100)
            )
            timestamp += 1
            time.sleep(0.5)
            
    # 在后台线程生成模拟数据
    data_thread = threading.Thread(target=simulate_data, daemon=True)
    data_thread.start()
    
    # 显示监控图表
    monitor.show()


@dataclass
class WarehouseMonitorInterface:
    """仓库监控系统 接口类"""
    monitor: Optional[RealtimeMonitor]=field(default=None)

    def collect_simulate_data(self,sim_obj_manager:SimObjManager,env_time:float)->None:
        """收集模拟数据"""
        if self.monitor is None or sim_obj_manager is None:return
        # 未放入分类平台的货物都是未处理的
        unprocessed= sum(len(truck.goods_list) for truck in sim_obj_manager.truck_dict.values() if truck.type=="in")
        # 分类平台上的货物
        """ 
        这个写法使用了两层 .get() 方法来防止 KeyError 和 AttributeError：
        第一层防护 sim_obj_manager.type_pool_dict.get("type_pool",{})：
        如果 type_pool_dict 中没有 "type_pool" 这个键，返回空字典 {}
        如果存在，返回对应的 TypePool 对象
        第二层防护 .get("goods_list",[])：
        如果第一层返回的是空字典 {}，则 {}.get("goods_list",[]) 返回空列表 []
        如果第一层返回的是 TypePool 对象，由于对象没有 get 方法，这里实际上会有问题

        type_pool_obj = sim_obj_manager.type_pool_dict.get("type_pool")
        if type_pool_obj:
            type_pool = len(type_pool_obj.goods_list)
        else:
            type_pool = 0
        """
        # 分类平台上的货物 - 使用更安全的访问方式
        type_pool_obj = sim_obj_manager.type_pool_dict.get("type_pool")
        type_pool = len(getattr(type_pool_obj, "goods_list", [])) if type_pool_obj else 0
        
        normal_pool_obj = sim_obj_manager.type_pool_dict.get("normal_type_pool")
        normal_pool = len(getattr(normal_pool_obj, "goods_list", [])) if normal_pool_obj else 0
        
        dangerous_pool_obj = sim_obj_manager.type_pool_dict.get("dangerous_type_pool")
        dangerous_pool = len(getattr(dangerous_pool_obj, "goods_list", [])) if dangerous_pool_obj else 0
        
        refrigerated_pool_obj = sim_obj_manager.type_pool_dict.get("refrigerated_type_pool")
        refrigerated_pool = len(getattr(refrigerated_pool_obj, "goods_list", [])) if refrigerated_pool_obj else 0
        
        agv_pickup = sum(len(agv_pickup.goods_list) for agv_pickup in sim_obj_manager.agv_pickup_point_dict.values())
        
        processed =GOODS_NUM-unprocessed-type_pool-normal_pool-dangerous_pool-refrigerated_pool-agv_pickup

        self.monitor.updata_data(
            timestamp=env_time,
            unprocessed=unprocessed,
            type_pool=type_pool,
            normal_pool=normal_pool,
            dangerous_pool=dangerous_pool,
            refrigerated_pool=refrigerated_pool,
            agv_pickup=agv_pickup,
            processed=processed
        )



        
    
   



        




        



